package com.zy.core.data.jpa.support.repository.impl;

import com.zy.core.data.jpa.support.entity.BaseEntity;
import com.zy.core.data.jpa.support.entity.page.Pagination;
import com.zy.core.data.jpa.support.repository.BaseRepository;
import com.zy.core.util.Finder;
import com.zy.core.util.MyBeanUtils;
import com.zy.core.util.Updater;
import org.hibernate.Session;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.query.Query;
import org.hibernate.transform.Transformers;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class BaseRepositoryImpl<T extends BaseEntity,ID extends Serializable> extends SimpleJpaRepository<T,ID>
        implements BaseRepository<T,ID> {

    private final Class<T> domainClass;
    private final EntityManager em;


    public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.domainClass = domainClass;
        this.em = entityManager;
    }



    @Override
    public Session getSession() {
        return this.em.unwrap(Session.class);
    }

    @Override
    public T findOne(ID id) {
        Optional<T> op = super.findById(id);
        //避免hibernate5.2抛出值不存在的异常,需要验证查询出的对象是否存在
        boolean present = op.isPresent();
        return !present ? null : op.get();
    }

    @Override
    public List findListByFinder(Finder finder) {
        Query query = finder.createQuery(getSession());
        List list = query.list();

        return list;
    }

    @Transactional(readOnly = false)
    @SuppressWarnings("unchecked")
    public T updateByUpdater(Updater<T> updater) {
        ClassMetadata cm = getSession().getSessionFactory().getClassMetadata(
                domainClass);
        T bean = updater.getBean();
        T po = (T) getSession().get(domainClass,
                cm.getIdentifier(bean, (SessionImplementor) getSession()));
        updaterCopyToPersistentObject(updater, po, cm);
        return po;
    }

    @Override
    public Pagination findPageByFinder(Finder finder, int pageNo, int pageSize) {
        int totalCount = this.countQueryResult(finder);
        Pagination p = new Pagination<T>(pageNo, pageSize, totalCount);
        if (totalCount < 1) {
            p.setList(new ArrayList());
            return p;
        }
        Query query = getSession().createQuery(finder.getOrigHql());
        finder.setParamsToQuery(query);
        query.setFirstResult(p.getFirstResult());
        query.setMaxResults(p.getPageSize());
        if (finder.isCacheable()) {
            query.setCacheable(true);
        }
        List list = query.list();
        p.setList(list);
        return p;
    }

    @Override
    public List findObjectListByFinder(Finder finder) {
        Query query = finder.createQuery(getSession());
        List<Object> list = query.list();
        return list;
    }

    @Override
    public List<Map<String, Object>> findMapByFinder(Finder finder) {
        Query query = finder.createQuery(getSession());

        List<Map<String, Object>> result = query.setResultTransformer(
                Transformers.ALIAS_TO_ENTITY_MAP).list();

        return result;
    }

    @Override
    public List<Object[]> findObjectArrayListByFinder(Finder finder) {
        Query query = finder.createQuery(getSession());
        List<?> list = query.list();
        return (List<Object[]>) list;
    }

    @Override
    public Double getAvgQueryResult(String sql) {
        Query query = getSession().createQuery(sql);
        return query.uniqueResult() == null ? 0 : (Double) query.uniqueResult();
    }

    @Override
    public List find(String hql, Object... values) {
        return createQuery(hql, values).list();
    }

    @Override
    public Object findUnique(String hql, Object... values) {
        return createQuery(hql, values).uniqueResult();
    }



    private void updaterCopyToPersistentObject(Updater<T> updater, T po,
                                               ClassMetadata cm) {
        //得到所有属性名称(不包括主键)
        String[] propNames = cm.getPropertyNames();
        //获取主键
        String identifierName = cm.getIdentifierPropertyName();
        T bean = updater.getBean();
        Object value;
        for (String propName : propNames) {

            if (propName.equals(identifierName)) {
                continue;
            }
            try {
                value = MyBeanUtils.getSimpleProperty(bean, propName);

                if (!updater.isUpdate(propName, value)) {
                    continue;
                }
                // cm.setPropertyValue(po, propName, value, POJO);

                cm.setPropertyValue(po, propName, value);
            } catch (Exception e) {
                throw new RuntimeException(
                        "copy property to persistent object failed: '"
                                + propName + "'", e);
            }
        }

    }

    @Override
    public int countQueryResult(Finder finder) {
        Query query = getSession().createQuery(finder.getRowCountHql());
        finder.setParamsToQuery(query);


        if (finder.isCacheable()) {
            //开启查询缓存
            query.setCacheable(true);
        }
        return ((Number) query.iterate().next()).intValue();
    }

    /**
     * 根据查询函数与参数列表创建Query对象,后续可进行更多处理,辅助函数.
     */
    protected Query createQuery(String queryString, Object... values) {
        Query queryObject = getSession().createQuery(queryString);
        if (values != null) {
            for (int i = 0; i < values.length; i++) {
                queryObject.setParameter(i, values[i]);
            }
        }
        return queryObject;
    }

    @Override
    public Class<T> getDomainClass() {
        return domainClass;
    }

    public EntityManager getEm() {
        return em;
    }
}
